<!DOCTYPE html>
<html class="has-navbar-fixed-top">
<head>
    <meta charset="utf-8">
<title>日期与时间 - wanzixin</title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">

<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/outdated-browser/1.1.5/outdatedbrowser.min.css">


<link href="/zh-cn/Study/Java/%E6%97%A5%E6%9C%9F%E4%B8%8E%E6%97%B6%E9%97%B4/" rel="alternate" hreflang="zh-CN" />
    


<meta name="description" content="">





    <meta name="description" content="日期和时间是计算机处理的重要数据，绝大部分程序的运行都要和时间打交道。本节我们将详细讲解Java程序如何正确处理日期和时间。">
<meta property="og:type" content="article">
<meta property="og:title" content="日期与时间">
<meta property="og:url" content="https://wanzixin.github.io/Study/Java/%E6%97%A5%E6%9C%9F%E4%B8%8E%E6%97%B6%E9%97%B4/index.html">
<meta property="og:site_name" content="wanzixin">
<meta property="og:description" content="日期和时间是计算机处理的重要数据，绝大部分程序的运行都要和时间打交道。本节我们将详细讲解Java程序如何正确处理日期和时间。">
<meta property="og:locale" content="en_US">
<meta property="article:published_time" content="2021-05-27T11:35:02.000Z">
<meta property="article:modified_time" content="2021-05-30T13:33:03.201Z">
<meta property="article:author" content="wanzixin">
<meta name="twitter:card" content="summary">





<link rel="icon" href="/favicon.png">


<link rel="stylesheet" href="//fonts.googleapis.com/css?family=Ovo|Source+Code+Pro">
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/bulma/0.6.2/css/bulma.min.css">


<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/lightgallery/1.6.8/css/lightgallery.min.css">
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/justifiedGallery/3.6.5/css/justifiedGallery.min.css">


<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/atom-one-light.min.css">


<link rel="stylesheet" href="/css/style.css">


<script defer src="//use.fontawesome.com/releases/v5.0.8/js/all.js"></script>


    
    
    
    
    
    
    
    
    
    

    


<meta name="generator" content="Hexo 5.4.0"></head>
<body>
    
<nav class="navbar is-transparent is-fixed-top navbar-main" role="navigation" aria-label="main navigation">
    <div class="container">
        <div class="navbar-brand">
            <a class="navbar-item navbar-logo" href="/">
                
                    
                    wanzixin
                    
                
            </a>
            <div class="navbar-burger">
                <span></span>
                <span></span>
                <span></span>
            </div>
        </div>
        
        <div class="navbar-menu navbar-start">
            
            <a class="navbar-item "
               href="/archives">Archives</a>
            
            <a class="navbar-item "
               href="/categories">Categories</a>
            
            <a class="navbar-item "
               href="/categories/Diary">Diary</a>
            
            <a class="navbar-item "
               href="/categories/Gallery">Gallery</a>
            
            <a class="navbar-item "
               href="/categories/Study">Study</a>
            
            <a class="navbar-item "
               href="/categories/Item">Item</a>
            
            <a class="navbar-item "
               href="/about">About</a>
            
        </div>
        
        <div class="navbar-menu navbar-end">
            
            <a class="navbar-item search" title="Search" href="javascript:;">
                <i class="fas fa-search"></i>
            </a>
            
            
            <div class="navbar-item is-hoverable has-dropdown is-hidden-mobile is-hidden-tablet-only toc">
                <a class="navbar-item" title="Table of Contents">
                    <i class="fa fa-list"></i>
                </a>
                <div class="navbar-dropdown is-right">
                    
                    
                    
                    
                    <a class="navbar-item" href="#基本概念">1&nbsp;&nbsp;<b>基本概念</b></a>
                    
                    
                    
                    <a class="navbar-item" href="#本地时间">1.1&nbsp;&nbsp;本地时间</a>
                    
                    
                    
                    <a class="navbar-item" href="#时区">1.2&nbsp;&nbsp;时区</a>
                    
                    
                    
                    <a class="navbar-item" href="#夏令时">1.3&nbsp;&nbsp;夏令时</a>
                    
                    
                    
                    <a class="navbar-item" href="#本地化">1.4&nbsp;&nbsp;本地化</a>
                    
                    
                    
                    <a class="navbar-item" href="#小结">1.5&nbsp;&nbsp;小结</a>
                    
                    
                    <hr class="navbar-divider">
                    
                    
                    <a class="navbar-item" href="#Date和Calender">2&nbsp;&nbsp;<b>Date和Calender</b></a>
                    
                    
                    
                    <a class="navbar-item" href="#标准库API">2.1&nbsp;&nbsp;标准库API</a>
                    
                    
                    
                    <a class="navbar-item" href="#Date">2.2&nbsp;&nbsp;Date</a>
                    
                    
                    
                    <a class="navbar-item" href="#Calender">2.3&nbsp;&nbsp;Calender</a>
                    
                    
                    
                    <a class="navbar-item" href="#TimeZone">2.4&nbsp;&nbsp;TimeZone</a>
                    
                    
                    <hr class="navbar-divider">
                    
                    
                    <a class="navbar-item" href="#LocalDateTime">3&nbsp;&nbsp;<b>LocalDateTime</b></a>
                    
                    
                    
                    <a class="navbar-item" href="#DateTimeFormatter">3.1&nbsp;&nbsp;DateTimeFormatter</a>
                    
                    
                    
                    <a class="navbar-item" href="#Duration和Period">3.2&nbsp;&nbsp;Duration和Period</a>
                    
                    
                    <hr class="navbar-divider">
                    
                    
                    <a class="navbar-item" href="#ZonedDateTime">4&nbsp;&nbsp;<b>ZonedDateTime</b></a>
                    
                    
                    
                    <a class="navbar-item" href="#时区转换">4.1&nbsp;&nbsp;时区转换</a>
                    
                    
                    <hr class="navbar-divider">
                    
                    
                    <a class="navbar-item" href="#DateTimeFormatter-1">5&nbsp;&nbsp;<b>DateTimeFormatter</b></a>
                    
                    
                    
                    <a class="navbar-item" href="#小结-1">5.1&nbsp;&nbsp;小结</a>
                    
                    
                    <hr class="navbar-divider">
                    
                    
                    <a class="navbar-item" href="#Instant">6&nbsp;&nbsp;<b>Instant</b></a>
                    
                    
                    <hr class="navbar-divider">
                    
                    
                    <a class="navbar-item" href="#最佳实践">7&nbsp;&nbsp;<b>最佳实践</b></a>
                    
                    
                    
                    <a class="navbar-item" href="#旧API转新API">7.1&nbsp;&nbsp;旧API转新API</a>
                    
                    
                    
                    <a class="navbar-item" href="#新API转旧API">7.2&nbsp;&nbsp;新API转旧API</a>
                    
                    
                    
                    <a class="navbar-item" href="#在数据库中存储日期和时间">7.3&nbsp;&nbsp;在数据库中存储日期和时间</a>
                    
                    
                    
                    <a class="navbar-item" href="#小结-2">7.4&nbsp;&nbsp;小结</a>
                    
                </div>
            </div>
            
            
            <a class="navbar-item" title="GitHub" target="_blank" rel="noopener" href="https://github.com/wanzixin">
                
                <i class="fab fa-github"></i>
                
            </a>
               
            
        </div>
    </div>
</nav>

    <section class="section">
    <div class="container">
    <article class="article content gallery" itemscope itemprop="blogPost">
    <h1 class="article-title is-size-3 is-size-4-mobile" itemprop="name">
        
            日期与时间
        
    </h1>
    <div class="article-meta columns is-variable is-1 is-multiline is-mobile is-size-7-mobile">
        <span class="column is-narrow">
            
                <span>May 27 2021</span>
            
        </span>
        
        <span class="column is-narrow article-category">
            <i class="far fa-folder"></i>
            <a class="article-category-link" href="/categories/Study/">Study</a><span>></span><a class="article-category-link" href="/categories/Study/Java/">Java</a>
        </span>
        
        
        <span class="column is-narrow">
            
            
            43 minutes read (About 6495 words)
        </span>
        
    </div>
    <div class="article-entry is-size-6-mobile" itemprop="articleBody">
    
        <html><head></head><body><p>日期和时间是计算机处理的重要数据，绝大部分程序的运行都要和时间打交道。本节我们将详细讲解Java程序如何正确处理日期和时间。<span id="more"></span></p>
<h2 id="基本概念"><a href="#基本概念" class="headerlink" title="基本概念"></a>基本概念</h2><p>日期是指某一天，它不是连续变化的，而应该被看作离散的。而时间有两种概念，一种是不带日期的时间，例如12:30:59；另一种是带日期的时间，例如2021-5-13 10:09:01，只有这种带日期的时间能唯一地确定某个时刻，不带日期的时间是无法确定一个唯一时刻的。</p>
<h3 id="本地时间"><a href="#本地时间" class="headerlink" title="本地时间"></a>本地时间</h3><p>当我们说当前时刻是2021年5月13日早上10:10时，我们说的实际上是本地时间，在国内就是北京时间。在这个时刻，如果地球上不同地方的人们同时看一眼手表，他们各自的本地时间是不同的。</p>
<p>所以，不同的时区，在同一时刻，本地时间是不同的。全球一共分为24个时区，伦敦所在的时区称为标准时区，其他地区按东/西偏移的小时区分，北京所在的时区是东八区。</p>
<h3 id="时区"><a href="#时区" class="headerlink" title="时区"></a>时区</h3><p>因为光靠本地时间还无法唯一确定一个准确的时刻，所以我们还需要给本地时间加上一个时区。</p>
<p>时区有好几种表达方式。一种是以GMT或UTC加时区偏移表示，例如：GMT+08:00或者UTC+08:00表示东八区。GMT和UTC可以认为基本是等价的，只是UTC使用更精确的原子钟计时，每隔几年会有一个闰秒，我们在开发程序时可以忽略两者的误差，因为计算机的时钟在联网时会自动与时间服务器同步时间。</p>
<p>另一种是缩写，例如CST表示China Standard Time，也就是中国标准时间，但是CST也可以表示美国中部时间Central Standard Time USA，因此，缩写容易产生误差，我们尽量不要使用缩写。</p>
<p>最后一种是以洲/城市表示，例如Asia/Shanghai，表示上海所在地的时区。要注意，城市名称不是任意的城市，而是由国际标准组织规定的城市。</p>
<p>因为有时区的存在，东八区的2019年11月20日早上8:15，和西五区的2019年11月19日晚上19:15，他们的时刻是相同的。时刻相同的意思就是，分别在两个时区的两个人，如果在这一刻通电话，他们各自报出自己手表的时间，虽然本地时间是不同的，但是这两个时间表示的时刻是相同的。</p>
<h3 id="夏令时"><a href="#夏令时" class="headerlink" title="夏令时"></a>夏令时</h3><p>所谓夏令时，就是夏天开始的时候，把时间往后拨一个小时，夏天结束的时候，再把时间往前拨一个小时。我们国家实行过一段夏令时，1992年就废除了，但是矫情的美国人现在还在使用，所以时间换算更加复杂。</p>
<p>因为涉及到夏令时，相同的时区，如果表示的方式不同，转换出的时间是不同的。我们举个栗子：</p>
<p>对于2019-11-20和2019-6-20两个日期来说，假设北京人在纽约：</p>
<ul>
<li>如果以GMT或者UTC作为时区，无论日期是多少，时间都是19:00</li>
<li>如果以国家／城市表示，例如America／NewYork，虽然纽约也在西五区，但是，因为夏令时的存在，在不同的日期，GMT时间和纽约时间可能是不一样的</li>
</ul>
<p>实行夏令时的不同地区，进入和退出夏令时的时间很可能是不同的。同一个地区，根据历史上是否实行过夏令时，标准时间在不同年份换算成当地时间也是不同的。因此，计算夏令时，没有统一的公式，必须按照一组给定的规则来算，并且，该规则要定期更新。<strong>计算夏令时请使用标准库提供的相关类，不要试图自己计算夏令时。</strong></p>
<h3 id="本地化"><a href="#本地化" class="headerlink" title="本地化"></a>本地化</h3><p>在计算机中，通常使用<code>Locale</code>表示一个国家或地区的日期、时间、数字、货币等格式。Locale由<code>语言_国家</code>的字母缩写构成，例如zh_CN表示中文+中国，en_US表示英文+美国。语言使用小写，国家使用大写。</p>
<p>对于日期来说，不同的Locale，例如中国和美国的表示方式如下：</p>
<ul>
<li>zh_CN：2016-11-30</li>
<li>en_US：11/30/2016</li>
</ul>
<p>计算机用Locale在日期、时间、货币和字符串之间进行转换。一个电商网站会根据用户所在的Locale对用户显示相应格式的内容。</p>
<h3 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h3><p>在编写日期和时间的程序前，我们要准确理解日期、时间和时刻的概念。</p>
<p>由于存在本地时间，我们需要理解时区的概念，并且必须牢记由于夏令时的存在，同一地区用<code>GMT/UTC</code>和城市表示的时区可能导致时间不同。</p>
<p>计算机通过Locale来针对当地用户习惯格式化日期、时间、数字、货币等。</p>
<h2 id="Date和Calender"><a href="#Date和Calender" class="headerlink" title="Date和Calender"></a>Date和Calender</h2><p>在理解日期和时间的表示方式之前，我们先要理解数据的存储和展示。</p>
<p>当我们定义一个整型变量并赋值时：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">int</span> n = <span class="hljs-number">123400</span>;<br></code></pre></td></tr></tbody></table></figure>

<p>编译器会把上述字符串（程序源码就是一个字符串）编译成字节码。在程序的运行期，变量<code>n</code>指向的内存实际上是一个4字节区域：</p>
<figure class="highlight plaintext hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs ascii">┌──┬──┬──┬──┐<br>│00│01│e2│08│<br>└──┴──┴──┴──┘<br></code></pre></td></tr></tbody></table></figure>

<p>注意到计算机内存除了二进制的<code>0</code>/<code>1</code>外没有其他任何格式。</p>
<p>当我们用<code>System.out.println(n)</code>打印这个整数的时候，实际上<code>println()</code>这个方法在内部把<code>int</code>类型转换成<code>String</code>类型，然后打印出字符串<code>123400</code>。</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">int</span> n = <span class="hljs-number">123400</span>;<br><span class="hljs-comment">// 123400</span><br>System.out.println(n);<br><span class="hljs-comment">// 1e208</span><br>System.out.println(Integer.toHexString(n));<br><span class="hljs-comment">// $123,400.00</span><br>System.out.println(NumberFormat.getCurrencyInstance(Locale.US).format(n));<br></code></pre></td></tr></tbody></table></figure>

<p>由此可见，整数<code>123400</code>只是数据的存储格式，而我们打印的各种各样字符串，是数据的展示格式。展示格式有多种形式，但本质上它就是一个转换方法。</p>
<p>理解了数据的存储和展示，我们回头看看以下几种日期和时间：</p>
<ul>
<li>2019-11-20 0:15:01 GMT+00:00</li>
<li>2019年11月20日8:15:01</li>
<li>11/19/2019 19:15:01 America/New_York</li>
</ul>
<p>它们实际上是数据的展示格式，分别按英国时区、中国时区、纽约时区对同一个时刻进行展示。而这个“同一个时刻”在计算机中存储的本质上只是一个整数，我们称它为<code>Epoch Time</code>。<code>Epoch Time</code>是计算从1970年1月1日零点（格林威治时区／GMT+00:00）到现在所经历的秒数，例如：<code>1574208900</code>表示从从1970年1月1日零点GMT时区到该时刻一共经历了1574208900秒。分别换算成伦敦，北京，纽约时间分别是：</p>
<figure class="highlight plaintext hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">1574208900 = 北京时间2019-11-20 8:15:00<br>           = 伦敦时间2019-11-20 0:15:00<br>           = 纽约时间2019-11-19 19:15:00<br></code></pre></td></tr></tbody></table></figure>

<p>因此，在计算机中只需要存储一个整数1574208900表示某一个时刻。当需要显示为某一地区的当地时间时，我们就把它格式化为一个字符串。</p>
<p><code>Epoch Time</code>又称为时间戳，在不同的编程语言中，会有几种存储方式：</p>
<ul>
<li>以秒为单位的整数：1574208900，缺点是精度只能到秒；</li>
<li>以毫秒为单位的整数：1574208900123，最后3位表示毫秒数；</li>
<li>以秒为单位的浮点数：1574208900.123，小数点后面表示零点几秒。</li>
</ul>
<p>而在Java程序中，时间戳通常是用<code>long</code>类型的毫秒数，即：</p>
<figure class="highlight plaintext hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">long t = 1574208900123L;<br></code></pre></td></tr></tbody></table></figure>

<p>转换成北京时间就是<code>2019-11-20T8:15:00.123</code>。要获取当前时间戳，可以使用<code>System.currentTimeMillis()</code>，这是Java程序获取时间戳最常用的方法。</p>
<h3 id="标准库API"><a href="#标准库API" class="headerlink" title="标准库API"></a>标准库API</h3><p>我们再来看一下Java标准库提供的API。Java标准库有两套处理日期和时间的API：</p>
<ul>
<li>一套定义在<code>java.util</code>这个包里面，主要包括<code>Date</code>、<code>Calendar</code>和<code>TimeZone</code>这几个类；</li>
<li>一套新的API是在Java 8引入的，定义在<code>java.time</code>这个包里面，主要包括<code>LocalDateTime</code>、<code>ZonedDateTime</code>、<code>ZoneId</code>等。</li>
</ul>
<p>为什么会有新旧两套API呢？因为历史遗留原因，旧的API存在很多问题，所以引入了新的API。</p>
<p>那么我们能不能跳过旧的API直接用新的API呢？如果涉及到遗留代码就不行，因为很多遗留代码仍然使用旧的API，所以目前仍然需要对旧的API有一定了解，很多时候还需要在新旧两种对象之间进行转换。</p>
<p>本节我们快速讲解旧API的常用类型和方法。</p>
<h3 id="Date"><a href="#Date" class="headerlink" title="Date"></a>Date</h3><p><code>java.util.Date</code>是用于表示一个日期和时间的对象，注意与<code>java.sql.Date</code>区分，后者用在数据库中。如果观察Date的源码，可以发现它实际上存储了一个long类型的以毫秒表示的时间戳。</p>
<p>我们来看看Date的基本用法：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 获取当前时间:</span><br>Date date = <span class="hljs-keyword">new</span> Date();<br>System.out.println(date.getYear() + <span class="hljs-number">1900</span>); <span class="hljs-comment">// 必须加上1900</span><br>System.out.println(date.getMonth() + <span class="hljs-number">1</span>); <span class="hljs-comment">// 0~11，必须加上1</span><br>System.out.println(date.getDate()); <span class="hljs-comment">// 1~31，不能加1</span><br><span class="hljs-comment">// 转换为String:</span><br>System.out.println(date.toString());<br><span class="hljs-comment">// 转换为GMT时区:</span><br>System.out.println(date.toGMTString());<br><span class="hljs-comment">// 转换为本地时区:</span><br>System.out.println(date.toLocaleString());<br></code></pre></td></tr></tbody></table></figure>

<p>注意<code>getYear()</code>返回的年份必须加上<code>1900</code>，<code>getMonth()</code>返回的月份是<code>0</code><del><code>11</code>分别表示1</del>12月，所以要加1，而<code>getDate()</code>返回的日期范围是<code>1</code>~`31`，又不能加1。</p>
<p>打印本地时区表示的日期和时间时，不同的计算机可能会有不同的结果。如果我们想要针对用户的偏好精确地控制日期和时间的格式，就可以使用<code>SimpleDateFormat</code>对一个<code>Date</code>进行转换。它用预定义的字符串表示格式化：</p>
<ul>
<li>yyyy：年</li>
<li>MM：月</li>
<li>dd: 日</li>
<li>HH: 小时</li>
<li>mm: 分钟</li>
<li>ss: 秒</li>
</ul>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 获取当前时间:</span><br>Date date = <span class="hljs-keyword">new</span> Date();<br><span class="hljs-keyword">var</span> sdf = <span class="hljs-keyword">new</span> SimpleDateFormat(<span class="hljs-string">"yyyy-MM-dd HH:mm:ss"</span>);<br>System.out.println(sdf.format(date));<br></code></pre></td></tr></tbody></table></figure>

<p><code>Date</code>对象有几个严重的问题：它不能转换时区，除了<code>toGMTString()</code>可以按<code>GMT+0:00</code>输出外，Date总是以当前计算机系统的默认时区为基础进行输出。此外，我们也很难对日期和时间进行加减，计算两个日期相差多少天，计算某个月第一个星期一的日期等。</p>
<h3 id="Calender"><a href="#Calender" class="headerlink" title="Calender"></a>Calender</h3><p><code>Calendar</code>可以用于获取并设置年、月、日、时、分、秒，它和<code>Date</code>比，主要多了一个可以做简单的日期和时间运算的功能。</p>
<p>我们来看<code>Calendar</code>的基本用法：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 获取当前时间:</span><br>Calendar c = Calendar.getInstance();<br><span class="hljs-keyword">int</span> y = c.get(Calendar.YEAR);<br><span class="hljs-keyword">int</span> m = <span class="hljs-number">1</span> + c.get(Calendar.MONTH);<br><span class="hljs-keyword">int</span> d = c.get(Calendar.DAY_OF_MONTH);<br><span class="hljs-keyword">int</span> w = c.get(Calendar.DAY_OF_WEEK);<br><span class="hljs-keyword">int</span> hh = c.get(Calendar.HOUR_OF_DAY);<br><span class="hljs-keyword">int</span> mm = c.get(Calendar.MINUTE);<br><span class="hljs-keyword">int</span> ss = c.get(Calendar.SECOND);<br><span class="hljs-keyword">int</span> ms = c.get(Calendar.MILLISECOND);<br>System.out.println(y + <span class="hljs-string">"-"</span> + m + <span class="hljs-string">"-"</span> + d + <span class="hljs-string">" "</span> + w + <span class="hljs-string">" "</span> + hh + <span class="hljs-string">":"</span> + mm + <span class="hljs-string">":"</span> + ss + <span class="hljs-string">"."</span> + ms);<br></code></pre></td></tr></tbody></table></figure>

<p>注意到<code>Calendar</code>获取年月日这些信息变成了<code>get(int field)</code>，返回的年份不必转换，返回的月份仍然要加1，返回的星期要特别注意，<code>1</code>~`7`分别表示周日，周一，……，周六。</p>
<p><code>Calendar</code>只有一种方式获取，即<code>Calendar.getInstance()</code>，而且一获取到就是当前时间。如果我们想给它设置成特定的一个日期和时间，就必须先清除所有字段：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 当前时间:Calendar c = Calendar.getInstance();// 清除所有:c.clear();// 设置2019年:c.set(Calendar.YEAR, 2019);// 设置9月:注意8表示9月:c.set(Calendar.MONTH, 8);// 设置2日:c.set(Calendar.DATE, 2);// 设置时间:c.set(Calendar.HOUR_OF_DAY, 21);c.set(Calendar.MINUTE, 22);c.set(Calendar.SECOND, 23);// 2019-09-02 21:22:23System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(c.getTime()));</span><br></code></pre></td></tr></tbody></table></figure>

<p>利用<code>Calendar.getTime()</code>可以将一个<code>Calendar</code>对象转换成<code>Date</code>对象，然后就可以用<code>SimpleDateFormat</code>进行格式化了。</p>
<h3 id="TimeZone"><a href="#TimeZone" class="headerlink" title="TimeZone"></a>TimeZone</h3><p>利用<code>Calendar</code>进行时区转换的步骤是：</p>
<ol>
<li>清除所有字段；</li>
<li>设定指定时区；</li>
<li>设定日期和时间；</li>
<li>创建<code>SimpleDateFormat</code>并设定目标时区；</li>
<li>格式化获取的<code>Date</code>对象（注意<code>Date</code>对象无时区信息，时区信息存储在<code>SimpleDateFormat</code>中）。</li>
</ol>
<p>因此，本质上时区转换只能通过<code>SimpleDateFormat</code>在显示的时候完成。</p>
<p><code>Calendar</code>也可以对日期和时间进行简单的加减，这里就不贴代码了。</p>
<h2 id="LocalDateTime"><a href="#LocalDateTime" class="headerlink" title="LocalDateTime"></a>LocalDateTime</h2><p>从Java 8开始，java.time包提供了新的日期和时间API，主要涉及的类型有：</p>
<ul>
<li>本地日期和时间：LocalDateTime，LocalDate，LocalTime</li>
<li>带时区的日期和时间：ZonedDateTime</li>
<li>时刻：Instant</li>
<li>时区：ZoneId，ZoneOffset</li>
<li>时间间隔：Duration</li>
</ul>
<p>以及一套新的用于取代SimpleDataFormat的格式化类型<code>DateTimeFormatter</code>。和旧的API相比，新API严格区分了时刻、本地日期、本地时间和带时区的日期时间，并且，对时间和日期运算更加方便。此外，新API修正了旧API不合理的常量设计：</p>
<ul>
<li>Month用1～12表示1月到12月</li>
<li>Week的范围用1～7表示周一到周日</li>
</ul>
<p>最后，新API的类型几乎全部是不变类型，可以放心使用不必担心被修改。</p>
<p>我们首先来看看最常用的<code>LocalDateTime</code>，它表示一个本地日期和时间。</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java">LocalDate d = LocalDate.now(); <span class="hljs-comment">// 当前日期LocalTime t = LocalTime.now(); // 当前时间LocalDateTime dt = LocalDateTime.now(); // 当前日期和时间System.out.println(d); // 严格按照ISO 8601格式打印System.out.println(t); // 严格按照ISO 8601格式打印System.out.println(dt); // 严格按照ISO 8601格式打</span><br></code></pre></td></tr></tbody></table></figure>

<p>本地日期和时间通过now()获取到的总是以当前默认时区返回的，和旧API不同，LocalDateTime、LocalDate、LocalTime默认严格按照ISO 8601规定的日期和时间格式进行打印。</p>
<p>反过来，通过制定的日期和时间创建LocalDateTime可以通过<code>of()</code>方法。</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 指定日期和时间:LocalDate d2 = LocalDate.of(2019, 11, 30); // 2019-11-30, 注意11=11月LocalTime t2 = LocalTime.of(15, 16, 17); // 15:16:17LocalDateTime dt2 = LocalDateTime.of(2019, 11, 30, 15, 16, 17);LocalDateTime dt3 = LocalDateTime.of(d2, t2);</span><br></code></pre></td></tr></tbody></table></figure>

<p>因为严格按照ISO 8601的格式，因此，将字符串转换为LocalDateTime就可以传入标准格式。</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java">LocalDateTime dt = LocalDateTime.parse(<span class="hljs-string">"2019-11-19T15:16:17"</span>);LocalDate d = LocalDate.parse(<span class="hljs-string">"2019-11-19"</span>);LocalTime t = LocalTime.parse(<span class="hljs-string">"15:16:17"</span>);<br></code></pre></td></tr></tbody></table></figure>

<p>注意ISO 8601规定的日期和时间分隔符是<code>T</code>。标准格式如下：</p>
<ul>
<li>日期：yyyy-MM-dd</li>
<li>时间：HH:mm:ss</li>
<li>带毫秒的时间：HH:mm:ss.SSS</li>
<li>日期和时间：yyyy-MM-dd’T’HH:mm:ss</li>
<li>带毫秒的日期和时间：yyyy-MM-dd’T’HH:mm:ss.SSS</li>
</ul>
<h3 id="DateTimeFormatter"><a href="#DateTimeFormatter" class="headerlink" title="DateTimeFormatter"></a>DateTimeFormatter</h3><p>如果要自定义输出格式，或者要把一个非ISO 8601格式的字符串解析成LocalDateTime，可以使用新的<code>DateTimeFormatter</code>。</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 自定义格式化:DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");System.out.println(dtf.format(LocalDateTime.now()));// 用自定义格式解析:LocalDateTime dt2 = LocalDateTime.parse("2019/11/30 15:16:17", dtf);System.out.println(dt2);</span><br></code></pre></td></tr></tbody></table></figure>

<p>LocalDateTime提供了对日期和时间进行加减的非常简单的链式调用。</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java">LocalDateTime dt = LocalDateTime.of(<span class="hljs-number">2019</span>, <span class="hljs-number">10</span>, <span class="hljs-number">26</span>, <span class="hljs-number">20</span>, <span class="hljs-number">30</span>, <span class="hljs-number">59</span>);System.out.println(dt);<span class="hljs-comment">// 加5天减3小时:LocalDateTime dt2 = dt.plusDays(5).minusHours(3);System.out.println(dt2); // 2019-10-31T17:30:59// 减1月:LocalDateTime dt3 = dt2.minusMonths(1);System.out.println(dt3); // 2019-09-30T17:30:59</span><br></code></pre></td></tr></tbody></table></figure>

<p>注意到月份加减会自动调用日期，例如从<code>2019-10-31</code>减去1个月得到的结果是<code>2019-09-30</code>，因为9月没有31日。</p>
<p>对日期和时间进行调整则使用<code>withXxx()</code>方法，例如：<code>withHour(15)</code>会把<code>10:11:12</code>变为<code>15:11:12</code>：</p>
<ul>
<li>调整年：withYear()</li>
<li>调整月：withMonth()</li>
<li>调整日：withDayOfMonth()</li>
<li>调整时：withHour()</li>
<li>调整分：withMinute()</li>
<li>调整秒：withSecond()</li>
</ul>
<p>实际上，LocalDateTime还有一个通用的<code>with()</code>方法允许我们做更复杂的运算。对于计算某个月第一个周日这样的问题，新的API可以轻松完成。</p>
<p>要判断两个LocalDateTime的先后，可以使用<code>isBefore()</code>，<code>isAfter()</code>方法，对于LocalDate和LocalTime类似。</p>
<p>注意到LocalDateTime无法与时间戳进行转换，因为它没有时区，无法确定某一时刻。后面我们介绍的<code>ZonedDateTime</code>相当于LocalDateTime加时区的组合，它具有时区，可以与long表示的时间戳进行转换。</p>
<h3 id="Duration和Period"><a href="#Duration和Period" class="headerlink" title="Duration和Period"></a>Duration和Period</h3><p>Duration表示两个时刻之间的时间间隔，Period表示两个日期之间的天数。</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java">LocalDateTime start = LocalDateTime.of(<span class="hljs-number">2019</span>, <span class="hljs-number">11</span>, <span class="hljs-number">19</span>, <span class="hljs-number">8</span>, <span class="hljs-number">15</span>, <span class="hljs-number">0</span>);LocalDateTime end = LocalDateTime.of(<span class="hljs-number">2020</span>, <span class="hljs-number">1</span>, <span class="hljs-number">9</span>, <span class="hljs-number">19</span>, <span class="hljs-number">25</span>, <span class="hljs-number">30</span>);Duration d = Duration.between(start, end);System.out.println(d); <span class="hljs-comment">// PT1235H10M30SPeriod p = LocalDate.of(2019, 11, 19).until(LocalDate.of(2020, 1, 9));System.out.println(p); // P1M21D</span><br></code></pre></td></tr></tbody></table></figure>

<p>注意到两个LocalDateTime之间的差值使用Duration表示，类似<code>PT1235H10M30S</code>，表示1235小时10分钟30秒。两个LocalDate之间的差值用Period表示，类似<code>P1M21D</code>，表示1个月21天。</p>
<p>Duration和Period的表示方法也符合ISO 8601的格式，它以<code>P...T...</code>的形式表示，<code>P...T</code>之间表示日期间隔，<code>T</code>后面表示时间间隔。如果是<code>PT...</code>的格式表示仅有时间间隔。利用<code>ofXxx()</code>或者<code>parse()</code>方法也可以直接创建Duration：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java">Duration d1 = Duration.ofHours(<span class="hljs-number">10</span>); <span class="hljs-comment">// 10 hoursDuration d2 = Duration.parse("P1DT2H3M"); // 1 day, 2 hours, 3 minutes</span><br></code></pre></td></tr></tbody></table></figure>

<h2 id="ZonedDateTime"><a href="#ZonedDateTime" class="headerlink" title="ZonedDateTime"></a>ZonedDateTime</h2><p>LocalDateTime总是表示本地日期和时间，要表示一个带时区的日期和时间，我们就需要<code>ZonedDateTime</code>。可以简单的把<code>ZonedDateTime</code>理解为<code>LocalDateTime</code>加<code>ZoneId</code>。<code>ZoneId</code>是<code>java.time</code>引入的新的时区类，注意和旧的<code>java.util.TimeZone</code>区分。</p>
<p>要创建一个ZonedDateTime对象，有以下几种方法，一种是通过<code>now()</code>方法返回当前时间。</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java">ZonedDateTime zbj = ZonedDateTime.now(); <span class="hljs-comment">// 默认时区ZonedDateTime zny = ZonedDateTime.now(ZoneId.of("America/New_York")); // 用指定时区获取当前时间System.out.println(zbj); // 2021-05-18T02:25:36.529532212Z[Etc/UTC]System.out.println(zny); // 2021-05-17T22:25:36.531996198-04:00[America/New_York]</span><br></code></pre></td></tr></tbody></table></figure>

<p>另一种方式是通过给一个LocalDateTime附加一个ZoneId，就可以变成ZonedDateTime。</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java">LocalDateTime ldt = LocalDateTime.of(<span class="hljs-number">2019</span>, <span class="hljs-number">9</span>, <span class="hljs-number">15</span>, <span class="hljs-number">15</span>, <span class="hljs-number">16</span>, <span class="hljs-number">17</span>);ZonedDateTime zbj = ldt.atZone(ZoneId.systemDefault());ZonedDateTime zny = ldt.atZone(ZoneId.of(<span class="hljs-string">"America/New_York"</span>));System.out.println(zbj); <span class="hljs-comment">// 2019-09-15T15:16:17Z[Etc/UTC]System.out.println(zny); // 2019-09-15T15:16:17-04:00[America/New_York]</span><br></code></pre></td></tr></tbody></table></figure>

<p>以这种方式创建的ZonedDateTime，它的日期和时间与LocalDateTime相同，但附加的时区不同，因此是两个不同的时刻。</p>
<h3 id="时区转换"><a href="#时区转换" class="headerlink" title="时区转换"></a>时区转换</h3><p>要转换时区，首先我们需要有一个<code>ZonedDateTime</code>对象，然后，通过<code>withZoneSameInstant()</code>将关联时区转换到另一个时区，转换后日期和时间都会相应调整。</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 以中国时区获取当前时间:ZonedDateTime zbj = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));// 转换为纽约时间:ZonedDateTime zny = zbj.withZoneSameInstant(ZoneId.of("America/New_York"));System.out.println(zbj); // 2021-05-18T10:31:08.109980611+08:00[Asia/Shanghai]System.out.println(zny); // 2021-05-17T22:31:08.109980611-04:00[America/New_York]</span><br></code></pre></td></tr></tbody></table></figure>

<p>要特别注意，由于夏令时的存在，时区转换时不同日期转换的结果很可能是不同的。涉及到时区时，千万不要自己计算时差，否则难以正确处理夏令时。</p>
<p>有了ZonedDateTime，将其转换为本地时间就非常简单了。转换为LocalDateTime，直接丢弃了时区信息。</p>
<figure class="highlight plaintext hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">ZonedDateTime zdt = ...LocalDateTime ldt = zdt.toLocalDateTime();<br></code></pre></td></tr></tbody></table></figure>

<h2 id="DateTimeFormatter-1"><a href="#DateTimeFormatter-1" class="headerlink" title="DateTimeFormatter"></a>DateTimeFormatter</h2><p>使用旧的<code>Date</code>对象时，我们用<code>SimpleDateFormat</code>进行格式化显示。使用新的<code>LocalDateTime</code>或<code>ZonedLocalDateTime</code>时，我们要进行格式化显示，就要使用<code>DateTimeFormatter</code>。</p>
<p>和<code>SimpleDateFormat</code>不同的是，<code>DateTimeFormatter</code>不但是不变对象，它还是线程安全的。线程的概念我们会在后面涉及到。现在我们只需要记住：因为<code>SimpleDateFormat</code>不是线程安全的，使用的时候，只能在方法内部创建新的局部变量。而<code>DateTimeFormatter</code>可以只创建一个实例，到处引用。</p>
<p>创建<code>DateTimeFormatter</code>时，我们仍然通过传入格式化字符串实现：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java">DateTimeFormatter formatter = DateTimeFormatter.ofPattern(<span class="hljs-string">"yyyy-MM-dd HH:mm"</span>);<br></code></pre></td></tr></tbody></table></figure>

<p>格式化字符串的使用方式与<code>SimpleDateFormat</code>完全一致。</p>
<p>另一种创建<code>DateTimeFormatter</code>的方法是，传入格式化字符串时，同时指定<code>Locale</code>：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java">DateTimeFormatter formatter = DateTimeFormatter.ofPattern(<span class="hljs-string">"E, yyyy-MMMM-dd HH:mm"</span>, Locale.US);<br></code></pre></td></tr></tbody></table></figure>

<p>这种方式可以按照<code>Locale</code>默认习惯格式化。我们来看实际效果。</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java">ZonedDateTime zdt = ZonedDateTime.now();<span class="hljs-keyword">var</span> formatter = DateTimeFormatter.ofPattern(<span class="hljs-string">"yyyy-MM-dd'T'HH:mm ZZZZ"</span>);System.out.println(formatter.format(zdt)); <span class="hljs-comment">// 2021-05-18T02:37 GMTvar zhFormatter = DateTimeFormatter.ofPattern("yyyy MMM dd EE HH:mm", Locale.CHINA);System.out.println(zhFormatter.format(zdt)); // 2021 5月 18 周二 02:37var usFormatter = DateTimeFormatter.ofPattern("E, MMMM/dd/yyyy HH:mm", Locale.US);System.out.println(usFormatter.format(zdt)); // Tue, May/18/2021 02:37</span><br></code></pre></td></tr></tbody></table></figure>

<p>当我们直接调用<code>System.out.println()</code>对一个<code>ZonedDateTime</code>或者<code>LocalDateTime</code>实例进行打印的时候，实际上，调用的是它们的<code>toString()</code>方法，默认的<code>toString()</code>方法显示的字符串就是按照<code>ISO 8601</code>格式显示的，我们可以通过<code>DateTimeFormatter</code>预定义的几个静态变量来引用：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">var</span> ldt = LocalDateTime.now();System.out.println(DateTimeFormatter.ISO_DATE.format(ldt)); <span class="hljs-comment">// 2019-09-15System.out.println(DateTimeFormatter.ISO_DATE_TIME.format(ldt)); // 2019-09-15T23:16:51.56217</span><br></code></pre></td></tr></tbody></table></figure>

<h3 id="小结-1"><a href="#小结-1" class="headerlink" title="小结"></a>小结</h3><p>对ZonedDateTime或LocalDateTime进行格式化，需要使用DateTimeFormmter类。</p>
<p>DateTimeFormatter可以通过格式化字符串和Locale对日期和字符串进行定制化输出。</p>
<h2 id="Instant"><a href="#Instant" class="headerlink" title="Instant"></a>Instant</h2><p>我们讲过，计算机存储的当前时间本质上是一个不断递增的整数。Java提供的<code>System.currentTimeMillis()</code>返回的就是以毫秒表示的当前时间戳。这个当前时间戳在<code>java.time</code>中以<code>Instant</code>类型表示，我们用<code>Instant.now()</code>获取当前时间戳，效果与<code>System.currentTimeMillis()</code>类似。<code>Instant</code>表示高精度时间戳。</p>
<p>实际上，Instant内部只有两个核心字段：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">final</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Instant</span> <span class="hljs-keyword">implements</span> ...</span>{  <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">long</span> seconds;  <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">int</span> nanos;}<br></code></pre></td></tr></tbody></table></figure>

<p>一个是以秒为单位的时间戳，一个是更精确的纳秒精度。它和<code>System.currentTimeMillis()</code>返回的long相比，只是多了更精确的纳秒。</p>
<p>即然Instant就是时间戳，那么，给它附加上一个时区，就可以创建出<code>ZonedDateTime</code>。</p>
<p>可见，对于某一个时间戳，给它关联上制定的ZoneId，就得到了ZonedDateTime，继而可以获得对应时区的LocalDateTime。所以，<code>LocalDateTime</code>，<code>ZoneId</code>，<code>Instant</code>，<code>ZonedDateTime</code>和<code>long</code>都可以互相转换。转换的时候，只需要留意long类型的单位是秒还是毫秒就行了。</p>
<h2 id="最佳实践"><a href="#最佳实践" class="headerlink" title="最佳实践"></a>最佳实践</h2><p>由于Java提供了新旧两套日期和时间的API，除非涉及到遗留代码，否则我们应该坚持使用新的API。如果需要与遗留代码打交道，如何在新旧API之间互相转换呢？</p>
<h3 id="旧API转新API"><a href="#旧API转新API" class="headerlink" title="旧API转新API"></a>旧API转新API</h3><p>如果要把旧的Date和Calendar转换为新API对象，可以通过<code>toInstant()</code>方法转换为Instant对象，再继续转换为ZonedDateTime。</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// Date -&gt; Instant:</span><br>Instant ins1 = <span class="hljs-keyword">new</span> Date().toInstant();<br><span class="hljs-comment">// Calendar -&gt; Instant -&gt; ZonedDateTime:</span><br>Calendar calendar = Calendar.getInstance();Instant ins2 = calendar.toInstant();ZonedDateTime zdt = ins2.atZone(calendar.getTimeZone().toZoneId());<br></code></pre></td></tr></tbody></table></figure>

<p>从上面的代码还可以看到，旧的<code>TimeZone</code>提供了一个<code>toZoneId()</code>，可以把自己变成新的<code>ZoneId</code>。</p>
<h3 id="新API转旧API"><a href="#新API转旧API" class="headerlink" title="新API转旧API"></a>新API转旧API</h3><p>如果要把新的ZonedDateTime转换为旧的API对象，只能借助long型时间戳做一个中转。</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// ZonedDateTime -&gt; long:</span><br>ZonedDateTime zdt = ZonedDateTime.now();<span class="hljs-keyword">long</span> ts = zdt.toEpochSecond() * <span class="hljs-number">1000</span>;<br><span class="hljs-comment">// long -&gt; Date:</span><br>Date date = <span class="hljs-keyword">new</span> Date(ts);<br><span class="hljs-comment">// long -&gt; Calendar:</span><br>Calendar calendar = Calendar.getInstance();calendar.clear();calendar.setTimeZone(TimeZone.getTimeZone(zdt.getZone().getId()));calendar.setTimeInMillis(zdt.toEpochSecond() * <span class="hljs-number">1000</span>);<br></code></pre></td></tr></tbody></table></figure>

<p>从上面的代码可以看到，新的ZoneId转换为旧的TimeZone，需要借助<code>ZoneId.getId()</code>返回的String完成。</p>
<h3 id="在数据库中存储日期和时间"><a href="#在数据库中存储日期和时间" class="headerlink" title="在数据库中存储日期和时间"></a>在数据库中存储日期和时间</h3><p>除了旧的java.util.Date，我们还可以找到另一个<code>java.sql.Date</code>，它继承自java.util.Date，但会自动忽略所有时间相关的信息。这个奇葩的设计原因要追溯到数据库的日期与时间类型。</p>
<p>在数据库中，也存在几种日期和时间类型：</p>
<ul>
<li><code>DATETIME</code>：表示日期和时间</li>
<li><code>DATE</code>：仅表示日期</li>
<li><code>TIME</code>：仅表示时间</li>
<li><code>TIMESTAMP</code>：和DATETIME类似，但是数据库在创建或者更新记录的时候同时修改TIMESTAMP</li>
</ul>
<p>在使用Java程序操作数据库时，我们需要把数据库类型与Java类型映射起来。下表是数据库类型与Java新旧API的映射关系：</p>
<table>
<thead>
<tr>
<th align="center">数据库</th>
<th align="center">对应Java类（旧）</th>
<th align="center">对应Java类（新）</th>
</tr>
</thead>
<tbody><tr>
<td align="center">DATETIME</td>
<td align="center">java.util.Date</td>
<td align="center">LocalDateTime</td>
</tr>
<tr>
<td align="center">DATE</td>
<td align="center">java.sql.Date</td>
<td align="center">LocalDate</td>
</tr>
<tr>
<td align="center">TIME</td>
<td align="center">java.sql.Time</td>
<td align="center">LocalTime</td>
</tr>
<tr>
<td align="center">TIMESTAMP</td>
<td align="center">java.sql.Timestamp</td>
<td align="center">LocalDateTime</td>
</tr>
</tbody></table>
<p>实际上，在数据库中，我们需要存储的最常用的是时刻（Instant），因为有了时刻信息，就可以根据用户自己选择的时区显示正确的本地时间。所以，最好的方法是直接用长整数long表示，在数据库中存储为<code>BIGINT</code>类型。</p>
<p>通过存储一个long型时间戳，我们可以编写一个<code>timestampToString()</code>方法，非常简单地为不同用户以不同的偏好来显示不同的本地时间。</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">static</span> String <span class="hljs-title">timestampToString</span><span class="hljs-params">(<span class="hljs-keyword">long</span> epochMilli, Locale lo, String zoneId)</span> </span>{  Instant ins = Instant.ofEpochMilli(epochMilli);  DateTimeFormatter f = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM, FormatStyle.SHORT);  <span class="hljs-keyword">return</span> f.withLocale(lo).format(ZonedDateTime.ofInstant(ins, ZoneId.of(zoneId)));  }<br></code></pre></td></tr></tbody></table></figure>

<h3 id="小结-2"><a href="#小结-2" class="headerlink" title="小结"></a>小结</h3><p>处理日期和时间时，尽量使用新的java.time包。</p>
<p>在数据库中存储时间戳时，尽量使用long型时间戳，它具有省空间、效率高、不依赖数据库的优点。</p>
</body></html>
    
    </div>
    
    
    <div class="columns is-mobile is-multiline article-nav">
        <span class="column is-12-mobile is-half-desktop  article-nav-prev">
            
            <a href="/Study/Java/%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95/">单元测试</a>
            
        </span>
        <span class="column is-12-mobile is-half-desktop  article-nav-next">
            
            <a href="/Gallery/gallery/">My Gallery Test</a>
            
        </span>
    </div>
    
</article>


<div class="sharebox">
    
<div class="sharethis-inline-share-buttons"></div>
<script type='text/javascript' src='//platform-api.sharethis.com/js/sharethis.js#property=608c1408daac690012507aa2&amp;product=sop' async='async'></script>

</div>



    </div>
</section>
    <footer class="footer">
    <div class="container">
        <div class="columns content">
            <div class="column is-narrow has-text-centered">
                &copy; 2021 wanzixin&nbsp;
                Powered by <a href="http://hexo.io/" target="_blank">Hexo</a> & <a
                        target="_blank" rel="noopener" href="http://github.com/ppoffice/hexo-theme-minos">Minos</a>
            </div>
            <div class="column is-hidden-mobile"></div>

            
            <div class="column is-narrow">
                <div class="columns is-mobile is-multiline is-centered">
                
                    
                <a class="column is-narrow has-text-black" title="GitHub" target="_blank" rel="noopener" href="https://github.com/ppoffice/hexo-theme-minos">
                    
                    GitHub
                    
                </a>
                
                </div>
            </div>
            
            
<div class="column is-narrow has-text-centered">
    <div class="dropdown is-up is-right is-hoverable" style="margin-top: -0.2em;">
        <div class="dropdown-trigger">
            <button class="button is-small" aria-haspopup="true" aria-controls="dropdown-menu7">
                <span class="icon">
                    <i class="fas fa-globe"></i>
                </span>
                <span>English</span>
                <span class="icon is-small">
            <i class="fas fa-angle-down" aria-hidden="true"></i>
          </span>
            </button>
        </div>
        <div class="dropdown-menu has-text-left" role="menu">
            <div class="dropdown-content">
            
                <a href="/Study/Java/%E6%97%A5%E6%9C%9F%E4%B8%8E%E6%97%B6%E9%97%B4/" class="dropdown-item">
                    English
                </a>
            
                <a href="/zh-cn/Study/Java/%E6%97%A5%E6%9C%9F%E4%B8%8E%E6%97%B6%E9%97%B4/" class="dropdown-item">
                    简体中文
                </a>
            
            </div>
        </div>
    </div>
</div>

        </div>
    </div>
</footer>
    <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.2/moment-with-locales.min.js"></script>

<!-- test if the browser is outdated -->
<div id="outdated">
    <h6>Your browser is out-of-date!</h6>
    <p>Update your browser to view this website correctly. <a id="btnUpdateBrowser" target="_blank" rel="noopener" href="http://outdatedbrowser.com/">Update my browser now </a></p>
    <p class="last"><a href="#" id="btnCloseUpdateBrowser" title="Close">&times;</a></p>
</div>
<script src="//cdnjs.cloudflare.com/ajax/libs/outdated-browser/1.1.5/outdatedbrowser.min.js"></script>
<script>
    $(document).ready(function () {
        // plugin function, place inside DOM ready function
        outdatedBrowser({
            bgColor: '#f25648',
            color: '#ffffff',
            lowerThan: 'flex'
        })
    });
</script>

<script>
    window.FontAwesomeConfig = {
        searchPseudoElements: true
    }
    moment.locale("en-AU");
</script>


    
    
<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.4/MathJax.js?config=TeX-MML-AM_CHTML"></script>
<script>
    MathJax.Hub.Config({
        "HTML-CSS": {
            matchFontHeight: false
        },
        SVG: {
            matchFontHeight: false
        },
        CommonHTML: {
            matchFontHeight: false
        },
        tex2jax: {
            inlineMath: [
                ['$','$'],
                ['\\(','\\)']
            ]
        }
    });
</script>

    
    
    
    
<script src="//cdnjs.cloudflare.com/ajax/libs/lightgallery/1.6.8/js/lightgallery-all.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/justifiedGallery/3.6.5/js/jquery.justifiedGallery.min.js"></script>
<script>
    (function ($) {
        $(document).ready(function () {
            if (typeof($.fn.lightGallery) === 'function') {
                $('.article.gallery').lightGallery({ selector: '.gallery-item' });
            }
            if (typeof($.fn.justifiedGallery) === 'function') {
                $('.justified-gallery').justifiedGallery();
            }
        });
    })(jQuery);
</script>

    
    
    <script src="https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.0/clipboard.min.js"></script>
    <style>
        .hljs {
            position: relative;
        }

        .hljs .clipboard-btn {
            float: right;
            color: #9a9a9a;
            background: none;
            border: none;
            cursor: pointer;
        }

        .hljs .clipboard-btn:hover {
          color: #8a8a8a;
        }

        .hljs > .clipboard-btn {
            display: none;
            position: absolute;
            right: 4px;
            top: 4px;
        }

        .hljs:hover > .clipboard-btn {
            display: inline;
        }

        .hljs > figcaption > .clipboard-btn {
            margin-right: 4px;
        }
    </style>
    <script>
      $(document).ready(function () {
        $('figure.hljs').each(function(i, figure) {
          var codeId = 'code-' + i;
          var code = figure.querySelector('.code');
          var copyButton = $('<button>Copy <i class="far fa-clipboard"></i></button>');
          code.id = codeId;
          copyButton.addClass('clipboard-btn');
          copyButton.attr('data-clipboard-target-id', codeId);

          var figcaption = figure.querySelector('figcaption');

          if (figcaption) {
            figcaption.append(copyButton[0]);
          } else {
            figure.prepend(copyButton[0]);
          }
        })

        var clipboard = new ClipboardJS('.clipboard-btn', {
          target: function(trigger) {
            return document.getElementById(trigger.getAttribute('data-clipboard-target-id'));
          }
        });
        clipboard.on('success', function(e) {
          e.clearSelection();
        })
      })
    </script>

    
    

    



<script src="/js/script.js"></script>


    
    <div class="searchbox ins-search">
    <div class="searchbox-mask"></div>
    <div class="searchbox-container ins-search-container">
        <div class="searchbox-input-wrapper">
            <input type="text" class="searchbox-input ins-search-input" placeholder="Type something..." />
            <span class="searchbox-close ins-close ins-selectable"><i class="fa fa-times-circle"></i></span>
        </div>
        <div class="searchbox-result-wrapper ins-section-wrapper">
            <div class="ins-section-container"></div>
        </div>
    </div>
</div>
<script>
    (function (window) {
        var INSIGHT_CONFIG = {
            TRANSLATION: {
                POSTS: 'Posts',
                PAGES: 'Pages',
                CATEGORIES: 'Categories',
                TAGS: 'Tags',
                UNTITLED: '(Untitled)',
            },
            CONTENT_URL: '/content.json',
        };
        window.INSIGHT_CONFIG = INSIGHT_CONFIG;
    })(window);
</script>

<script src="/js/insight.js"></script>

    
</body>
</html>